from absl import app, flags
from functools import reduce
import pandas as pd
import numpy as np

from xunkemgmt_client.client.util.query_util import (
    query_exchange_rates,
    query_balance_snapshots,
    query_position_snapshots)
from coin.support.accounting.logic.format_util import move_df_nan_back
from coin.support.accounting.app.balance_printer import BALANCE_BUSINESS_UNIT_LIST
from coin.support.accounting.logic.query_util import is_accounting_redundant
from xunkemgmt_client.client.util.query_util import query_accounts
from xunkemgmt_client.tool.slack_noti import send_to_slack

FLAGS = flags.FLAGS

def get_all_account_data(business_units):
  all_account_data = pd.DataFrame()
  all_account_data = query_accounts(active=True, business_units=business_units)
  all_account_data.sort_values('account_id', inplace=True)
  all_account_data.index = range(len(all_account_data))
  return all_account_data


def query_account_info(account_data):
  balance_data = query_balance_snapshots(as_proto=True)
  balance_df = pd.DataFrame([{'account_id': balance.balance_info.account_id,
                  'accounting_currency': balance.estimate.accounting_currency,
                  'total_value': balance.estimate.total_value}
                  for balance in balance_data])
  data = account_data.merge(balance_df, how="left", on="account_id")
  datat = pd.pivot_table(data,
                         index=['market_type', 'exchange', 'owner'],
                         columns='accounting_currency',
                         values='total_value',
                         aggfunc=lambda x: x)
  datat = move_df_nan_back(datat, datat.columns)
  datat.loc[:, "VALID"] = reduce(
      lambda x, y: x | y, [~datat[c].isnull() for c in datat.columns])
  datat.loc['Total'] = datat.sum(axis=0, numeric_only=True)
  return datat


def query_position_info(account_data):
  position_data = query_position_snapshots(as_proto=True)
  position_df = pd.DataFrame([{'account_id': position.position_info.account_id,
                   'accounting_currency': position.estimate.accounting_currency,
                   'total_value': position.estimate.total_value}
                   for position in position_data])
  data = account_data.merge(position_df, how="left", on="account_id")
  datat = pd.pivot_table(data,
                         index=['market_type', 'exchange', 'owner'],
                         columns='accounting_currency',
                         values='total_value',
                         aggfunc=lambda x: x)
  datat = move_df_nan_back(datat, datat.columns)
  datat.loc[:, "VALID"] = reduce(
      lambda x, y: x | y, [~datat[c].isnull() for c in datat.columns])
  datat.loc['Total'] = datat.sum(axis=0, numeric_only=True)
  datat.columns = [x[0] if isinstance(x, tuple) else x for x in datat.columns]
  return datat


def exchange_rate_balance(account_info):
  other_values_list = ['EUR', 'KRW', 'JPY', 'BTC']
  account_info = account_info[account_info.columns[account_info.loc['Total'] != 0]]
  account_data = account_info.loc['Total']
  valid_sum = account_data.loc['VALID']
  account_data = account_data.iloc[:-1]
  coinlist = list(account_data.index) + other_values_list
  exchange_rate_list = query_exchange_rates(coinlist, 'USD')
  exchange_rate = dict(zip([x + '-USD' for x in coinlist], exchange_rate_list))
  USD_value = [
      account_data.loc[x] * exchange_rate[x + '-USD'] for x in account_data.index
  ]
  balance_df = pd.DataFrame(account_data)
  balance_df.index = [x for x in balance_df.index]
  balance_df.columns = ['init']
  balance_df['USD_balance'] = USD_value
  for v in other_values_list:
    balance_df[v + '_balance'] = [x / exchange_rate[v + '-USD'] for x in USD_value]
  balance_df.loc['Total'] = balance_df.sum(axis=0)
  balance_df.loc['Total', 'init'] = '-'
  balance_df.loc['VALID'] = valid_sum
  return balance_df


def formatNumber(number):
  if type(number) == str:
    return number
  numStr = '%.0f' % number
  formatStr = ''
  numStr = numStr[::-1]
  i = 0
  while i < len(numStr):
    formatStr += numStr[i]
    i += 1
    if i % 3 == 0:
      formatStr += ','
  formatStr = formatStr.strip(',')
  formatStr = formatStr[::-1]
  return formatStr


def format_df(df):
  for col in df.columns:
    df[col] = df[col].map(formatNumber)
  return df


def ignore_accounts(account_data):
  for idx in account_data.index:
    if is_accounting_redundant(
      account_data.loc[idx, 'market_type'],
      account_data.loc[idx, 'exchange'],
      account_data.loc[idx, 'owner']):
      account_data.loc[idx] = None
  return account_data.dropna()


def report_estimate(account_info):
  balance_df = exchange_rate_balance(account_info)
  balance_df = format_df(balance_df)
  msg = balance_df.to_string()
  print(msg)
  msg = "```" + msg + "```"
  send_to_slack(msg, 'coin_accounting_noti', 'msg')


def report_accounts_diff(old_accounts_df, new_accounts_df, report_type):
  new_accounts_df['new'] = 1
  old_accounts_df['old'] = 1
  diff = pd.merge(old_accounts_df,
                  new_accounts_df,
                  on=['market_type', 'exchange', 'owner'],
                  how='outer')
  new_add_accounts = diff[np.isnan(diff['old'])]
  if len(new_add_accounts) > 0:
    new_add_accounts = new_add_accounts[['market_type', 'exchange', 'owner']]
    new_add_accounts.index = range(len(new_add_accounts))
    print(new_add_accounts)
    msg = new_add_accounts.to_string()
    msg = "```" + "new accounts:\n" + msg + "```"
    send_to_slack(msg, 'coin_accounting_noti', 'msg')
  missing_accounts = diff[np.isnan(diff['new'])]
  if len(missing_accounts) > 0:
    missing_accounts = missing_accounts[['market_type', 'exchange', 'owner']]
    missing_accounts.sort_values(missing_accounts.columns.tolist(), inplace=True)
    missing_accounts.index = range(len(missing_accounts))
    print(missing_accounts)
    msg = missing_accounts.to_string()
    msg = f"```abnormal {report_type}:\n{msg}```"

    send_to_slack(msg, 'coin_accounting_noti', 'msg')


def report_abnormal_balance(account_data, balance_info):
  account_data = account_data[['market_type', 'exchange', 'owner']]
  account_data = ignore_accounts(account_data)
  new_account_list = balance_info[balance_info['VALID'] == 1].index.tolist()
  new_accounts_df = pd.DataFrame(new_account_list)
  new_accounts_df.columns = ['market_type', 'exchange', 'owner']
  report_accounts_diff(account_data, new_accounts_df, 'balances')


def report_abnormal_position(account_data, position_info):
  account_data = account_data[['market_type', 'exchange', 'owner']]
  account_data = account_data[account_data['market_type'].isin(['Futures', 'Options'])]
  new_account_list = position_info[position_info['VALID'] == 1].index.tolist()
  new_accounts_df = pd.DataFrame(new_account_list)
  new_accounts_df.columns = ['market_type', 'exchange', 'owner']
  report_accounts_diff(account_data, new_accounts_df, 'positions')
  

def main(_):
  business_units = [elem.strip() for elem in FLAGS.business_unit.split(',')]
  account_data = get_all_account_data(business_units)
  account_info = query_account_info(account_data)
  report_estimate(account_info)
  report_abnormal_balance(account_data, account_info)

  position_info = query_position_info(account_data)
  report_abnormal_position(account_data, position_info)


if __name__ == '__main__':
  flags.DEFINE_string('business_unit', ','.join(BALANCE_BUSINESS_UNIT_LIST), '')
  app.run(main)
